﻿using System;
using System.Diagnostics;
using Microsoft.Practices.Unity;
using Rhino.Mocks;

namespace TestByWire
{
    public abstract class TargetInvoker
    {
        [ThreadStatic]
        protected internal static IUnityContainer Container;

        [ThreadStatic]
        protected internal static Type TargetTypeInContext;

        internal protected TargetInvoker(Type targetType)
        {
            if (targetType != null)
            {
                TargetTypeInContext = targetType;
            }

            Debug.Assert(targetType != null);

            Container = new UnityContainer();
            Container.AddExtension(new AutoInjectionBehavior(targetType)); //<TImplementation>>();

            Then.Reset(Container);
            Recorder.Reset(Container);
            DynamicDeclarations.Reset();
        }
    }

    public class TargetInvoker<TInterface, TImplementation> : TargetInvoker
        where TImplementation : class, TInterface
    {
        private readonly ITestTargetBuilder _testTargetBuilder;
        private Boolean _isSet;

        public event EventHandler<TargetInitializedArgs<TImplementation>> TestTargetInitialized;

        public TargetInvoker()
            : this(new TestTargetBuilder())
        {
        }

        public TargetInvoker(ITestTargetBuilder testTargetBuilder)
            : base(typeof(TImplementation))
        {
            if (testTargetBuilder == null)
            {
                throw new ArgumentNullException("testTargetBuilder");
            }

            _testTargetBuilder = testTargetBuilder;
        }

        public TInterface Calling
        {
            get
            {
                if (!_isSet)
                {
                    Then.Target = _testTargetBuilder.InitializeTestTarget();

                    RaiseTestTargetInitialized((TImplementation)Then.Target);

                    Recorder.Transfer();

                    _isSet = true;
                }

                return (TInterface)Then.Target;
            }
        }

        private void RaiseTestTargetInitialized(TImplementation instance)
        {
            EventHandler<TargetInitializedArgs<TImplementation>> handler = TestTargetInitialized;
            if (handler != null)
            {
                handler(this, new TargetInitializedArgs<TImplementation>(instance));
            }
        }
    }

    public class When<T> : TargetInvoker where T : class
    {
        protected internal When()
            : base(null)
        {
        }

        public static void RaiseEvent(Action<T> action, params object[] args)
        {
            RaiseEvent(default(string), action, args);
        }

        public static void RaiseEvent(string stubName, Action<T> action, params object[] args)
        {
            if (Then.Target == null)
            {
                Then.Target = new TestTargetBuilder().InitializeTestTarget();
                Recorder.Transfer();
            }

            var eventRaiser = Container.Resolve<T>(stubName);
            eventRaiser.Raise(action, args);
        }
    }
}
